//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
//////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2003 Intrinsyc Europe Ltd.
//////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2004 Siemens AG
//
// Version: 1.1.2.5
// Date:    11. Jan. 2006
//////////////////////////////////////////////////////////////////////////////
//
//
// (C) Copyright 2007 Marvell International Ltd.
//  All Rights Reserved
//


#include "precomp.h"
#include <rilbacklight.h>

//
// Line status values
//
static const DWORD g_rgdwLineStats[] =
{
    RIL_LINESTAT_READY,             // 0
    RIL_LINESTAT_UNAVAILABLE,       // 1
    RIL_LINESTAT_UNKNOWN,           // 2
    RIL_LINESTAT_RINGING,           // 3
    RIL_LINESTAT_CALLINPROGRESS,    // 4
    RIL_LINESTAT_ASLEEP,            // 5
};
#define NUM_LINESTATS   (sizeof(g_rgdwLineStats) / sizeof(DWORD))

// Defines an arbitrary maximum string length for the dial command.
// This maximum is not strictly enforced. It is used only as an aid
// for reporting a more specific error (RIL_E_DIALSTRINGTOOLONG) if
// the radio returns an error that is too generic.
#define MAX_DIAL_STRING_LENGTH      43

// Limits the maximum amount of DTMF tones which can be played with one AT command.
#define MAX_DTMF_TONES_PER_LINE     54

#ifdef RIL_FAKECSQ
extern volatile BOOL g_fFakeSignalStrength;
#endif

extern int g_iHideCallerId;
BOOL g_fCallWaiting = FALSE;
BOOL g_fEmergencyCall = FALSE;
char g_szEmergencyNo[MAX_EMERGENCYNO];

#define MAX_TRACKED_CALL_ID             10
BOOL g_rgfCallsInProgress[MAX_TRACKED_CALL_ID]; // = { 0 };   // Initialize to FALSE
static HRESULT ParseGetCallList(LPCSTR szRsp, void*& pBlob, UINT& cbBlob, LPVOID pParam);
static HRESULT ParseGetCallListCheck(LPCSTR szRsp, void*& pBlob, UINT& cbBlob, LPVOID pParam);
BOOL g_fAudioPathActivated = FALSE;
BOOL g_fReleaseHeldCall = FALSE;
BOOL g_fInitMicroTemp = FALSE;
extern RHA_FUNCTION_LIST RHAFunctionList;
extern DWORD g_dwSIMLockedState;
extern BOOL g_bRadioOff;
extern BOOL g_fNoMuteCmdSoFar;

RILCALLINFO g_CallInfo[MAX_NUMBERCALLS];
INT g_iNrCallInfo = -1;
CRITICAL_SECTION g_CallInfoSection;
DWORD g_LastATD = 0;

BOOL IsACallActive()
{
    BOOL bCallActive = FALSE;

    if (g_iNrCallInfo > 0) {
        EnterCriticalSection(&g_CallInfoSection);
        if (g_iNrCallInfo > 0) {
            INT i;

            for (i=0; i<g_iNrCallInfo; i++) {
                if (g_CallInfo[i].dwStatus==RIL_CALLSTAT_ACTIVE) {
                    bCallActive = TRUE;
                    break;
                }
            }
        }
        LeaveCriticalSection(&g_CallInfoSection);
    }

    return bCallActive;
}

static BOOL IsEmergencyNumber(LPCSTR szAddress)
{
    return (!strcmp(szAddress, "911") || !strcmp(szAddress, "112") || !strcmp(szAddress, "08"));
}

// The MC75 V2.001 needs AT+CMUT=0 to be set explicitely after each ATD and ATA.
DWORD WINAPI SetAudioMutingThreadProc(LPVOID pVoid)
{
    CRilHandle* pHandle;

    pHandle = (CRilHandle *)pVoid;

    Sleep(500);     // If you send AT+CMUT=0 too early it will fail.
    QueueInternalCmd(COMMAND_PORT, pHandle, "AT+CMUT=0\r", CMDOPT_NONE, APIID_SETAUDIOMUTING, 0, 0, 0, NULL, NULL);

    return 0;
}

static BOOL IsDialCommandInQueue(void* pItem, DWORD dwData)
{
    CCommand* pCmd = (CCommand*)pItem;
    BOOL* pFound = (BOOL *)dwData;

    if (pCmd->FDial())
        *pFound = TRUE;

    return FALSE;
}

// Set audio shortly before dialing
static HRESULT PreInitDialing(CRilHandle * pHandle, COM_PORT_TYPE iPort)
{
    TBD_FUNCTION(PreInitDialing);
    CCommand *pCmd;
    
    // Do not set the audio setting the same time the initial AT+CLCC is running on the URC port.
    while (!g_bRadioOff) {
        pCmd = pHandle->GetCurrentCommand(URC_PORT);
        if (pCmd != NULL) {
            char *szCmd;

            szCmd = pCmd->GetCmd();
            if (szCmd != NULL) {
                if (strcmp(szCmd, "AT+CLCC\r") != 0)
                    break;
            }
            else
                break;
        }
        else
            break;
        Sleep(25);
    }
    
    g_LastATD = GetTickCount();
    RHAFunctionList.pfnRHA_ActivateVoicePath(pHandle->GetAudioTxDevice(), pHandle->GetAudioRxDevice());
    g_fAudioPathActivated = TRUE;

    if (g_dwModemType == MODEMTYPE_SIEMENS_MC75 && g_dwModemVersion == 2001 && !g_bRadioOff)
        CreateThread(NULL, 0, SetAudioMutingThreadProc, (LPVOID)pHandle, 0, 0);
    
    return S_OK;
}

// Set audio shortly before answering
static HRESULT PreInitAnswer(CRilHandle * pHandle, COM_PORT_TYPE iPort)
{
    TBD_FUNCTION(PreInitAnswer);
    RHAFunctionList.pfnRHA_ActivateVoicePath(pHandle->GetAudioTxDevice(), pHandle->GetAudioRxDevice());
    g_fAudioPathActivated = TRUE;
    g_fCallWaiting = FALSE;

    if (g_dwModemType == MODEMTYPE_SIEMENS_MC75 && g_dwModemVersion == 2001)
        CreateThread(NULL, 0, SetAudioMutingThreadProc, (LPVOID)pHandle, 0, 0);
    
    return S_OK;
}

DWORD GetSNFMTimeout()
{
    DWORD dwTimeout;
    
    dwTimeout = 0;
    GetRegistryDWORD(HKEY_LOCAL_MACHINE, g_tszRegKeyRIL, TEXT("InitMicroTimeout"), &dwTimeout);
    if (dwTimeout == 0)
        dwTimeout = DEFAULT_SNFM_TIMEOUT;

    return dwTimeout;
}
        

void ActivateMicrophone(CRilHandle *pRILHandle)
{
    // We can control the microphone power supply manually. To do this, we have to acticate power
    // with the AT^SNFM command before starting voice call.
    if (g_fInitMicroTemp && !g_fAudioPathActivated)
    {
        QueueInternalCmdBlocking(URC_PORT, pRILHandle, "AT^SNFM=,1\r", CMDOPT_NONE, APIID_NONE, GetSNFMTimeout(), 0, 0);
    }
 }

// We have to save the dialed emergency number (112, 911 or 08) to be able
// do generate the correct faked AT+CLCC response.
void SetEmergencyNumber(LPCSTR szAddress)
{
    if (szAddress == NULL || strlen(szAddress) >= MAX_EMERGENCYNO)
        strcpy(g_szEmergencyNo, "112");
    else
        strcpy(g_szEmergencyNo, szAddress);
    g_fEmergencyCall = TRUE;
}
//
//
//
HRESULT RILDrv_Dial(DWORD dwParam, LPCSTR szAddress, DWORD dwType, DWORD dwOptions)
{
    TBD_FUNCTION(RILDrv_Dial);
#if defined (NODIALING) && !defined (SHIP_BUILD)
    bool fNoDialing;
    bool fNoEmergencyDialing;
#endif // NODIALING
    bool fNoBlackList;

    CNotificationData* pnd = NULL;
    RILCONNECTINFO rci; memset(&rci,0,sizeof(rci)); // zero struct
    char szCmd[MAX_ATCMD_LEN];
    LPSTR szWalk = szCmd;
    LPSTR szDialStringStart = NULL;
    HRESULT hr = S_OK;
    LPCSTR szAddrWalk;
    BOOL fDataPort = FALSE;
    DWORD dwDialOpts = CMDOPT_DIAL|CMDOPT_RETRYONSIMLOCKED;

    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    // When the radio is off, we shouldn't try to dial.
    if (g_bRadioOff) {
        hr = RIL_E_RADIOOFF;
        goto Error;
    }

    // While in SIM initialization state we shouldn't try to make a call on an MC75.
    if (!IsEmergencyNumber(szAddress) && IS_ADI && (g_dwReadyState & (RIL_READYSTATE_SIM|RIL_READYSTATE_UNLOCKED|RIL_READYSTATE_SMS)) != (RIL_READYSTATE_SIM|RIL_READYSTATE_UNLOCKED|RIL_READYSTATE_SMS)) {
        hr = RIL_E_NETWKTEMPFAILURE;
        goto Error;
    }

    // When SIM is not ready, we cannot do anything else than emergency call.
    if (!IsEmergencyNumber(szAddress) && !pHandle->GetDevice()->IsSIMReady()) {
        hr = RIL_E_SIMNOTINSERTED;
        goto Error;
    }

    // When SIM is ready, but PIN not entered, we cannot do anything else than emergency call.
    if (!IsEmergencyNumber(szAddress) && g_dwSIMLockedState != RIL_LOCKEDSTATE_READY && g_dwSIMLockedState != RIL_LOCKEDSTATE_UNKNOWN) {
        hr = RIL_E_SIMPINREQUIRED;
        goto Error;
    }

    g_fEmergencyCall = FALSE;
    g_fReleaseHeldCall = FALSE;


    if (!IS_ADI) {
        // MC75: blacklist is configured at startup
        // Other modules: blacklist has to be deactivated for each call
        if (GetRegistryBoolean(HKEY_LOCAL_MACHINE, g_tszRegKeyRIL, TEXT("NoBlackList"), &fNoBlackList) && fNoBlackList) {
            QueueInternalCmdBlocking(COMMAND_PORT, pHandle->GetDevice(), "AT^SBLK\r", CMDOPT_NONE, APIID_NONE, 1000, 0, 0);
        }
    }

#if defined (NODIALING) && !defined (SHIP_BUILD)
    // See if we want to disable dialing
    if (GetRegistryBoolean(HKEY_LOCAL_MACHINE, g_tszRegKeyRIL, TEXT("NoDialing"), &fNoDialing) && fNoDialing) {
        // We can't allow any dialing (for stress tests)
        TBD_OUTPUT(TBDCT_INFO, TBDOL_ERROR, TEXT("RILDrv_Dial : Dialing is prevented: %s"), TString(szAddress));
        hr = E_FAIL;
        goto Error;
    }

    // See if we want to disable emergency dialing
    if (GetRegistryBoolean(HKEY_LOCAL_MACHINE, g_tszRegKeyRIL, TEXT("NoEmergencyDialing"), &fNoEmergencyDialing) &&
        fNoEmergencyDialing) {
        if (IsEmergencyNumber(szAddress)) {
            // We can't allow dialing of emergency numbers (for stress tests)
            TBD_OUTPUT(TBDCT_INFO, TBDOL_ERROR, TEXT("RILDrv_Dial : Dialing of an emergency number is prevented: %s"), TString(szAddress));
            hr = E_FAIL;
            goto Error;
        }
    }
#endif // NODIALING

    szWalk = BeginLineSpecificCommand(szCmd, MAX_ATCMD_LEN, 0);

    switch (dwType)
    {
        case RIL_CALLTYPE_VOICE:
            pnd = new CNotificationData;
            if (pnd) {
                rci.cbSize = sizeof(RILCONNECTINFO);
                rci.dwParams = RIL_PARAM_CNI_CALLTYPE;
                rci.dwCallType = RIL_CALLTYPE_VOICE;
                if (!pnd->InitFromRealBlob(RIL_NOTIFY_CONNECT, &rci, sizeof(RILCONNECTINFO))) {
                    delete pnd;
                    pnd = NULL;
                }
            }

            if (!IS_ADI && IsACallActive())
            {
                // Depending on the SIM card it may be necessary to put the active call
                // on hold manually before initiating the new one.
                // MC75/TC63 do this automaticly.
                strncpyz(szWalk,"+CHLD=2;",MAX_ATCMD_LEN - (szWalk - szCmd));
                szWalk = strchr(szWalk, '\0');
            }
            if(g_dwModemType == MODEMTYPE_SIEMENS_MC45 || g_dwModemType == MODEMTYPE_SIEMENS_MC46)
            {
                if (IsEmergencyNumber(szAddress)) {
                    strncpyz(szWalk,"D",MAX_ATCMD_LEN - (szWalk - szCmd));
                    SetEmergencyNumber(szAddress);
                } else {
                    if(dwOptions & RIL_DIALOPT_PRESENTID || dwOptions & RIL_DIALOPT_RESTRICTID || g_iHideCallerId==0)
                    {
                        strncpyz(szWalk,"D",MAX_ATCMD_LEN - (szWalk - szCmd));
                    }
                    else
                    {
                        if(g_iHideCallerId == RIL_SVCSTAT_ENABLED)
                            strncpyz(szWalk,"D#31#",MAX_ATCMD_LEN - (szWalk - szCmd));
                        else
                            strncpyz(szWalk,"D*31#",MAX_ATCMD_LEN - (szWalk - szCmd));
                    }
                }
            }
            else
            {
                if (IsEmergencyNumber(szAddress))
                    SetEmergencyNumber(szAddress);
                strncpyz(szWalk,"D",MAX_ATCMD_LEN - (szWalk - szCmd));
            }
            break;

        case RIL_CALLTYPE_DATA:
            if(!pHandle->GetDevice()->GetDataAllowed())
            {
                RILRetailTrace((TEXT("RilDrv: Error - data connections not allowed while initializing\r\n")));
                hr = E_FAIL;
                goto Error;
            }

#ifdef RIL_FAKECSQ
            g_fFakeSignalStrength = TRUE;
#endif
            // If the last call had been a fax call, we have to switch back to data.
            strncpyz(szWalk,"+FCLASS=0;D",MAX_ATCMD_LEN - (szWalk - szCmd));
            fDataPort = TRUE;
            break;

        case RIL_CALLTYPE_FAX:
            (void)strncpyz(szWalk, "+FCLASS=1;D", MAX_ATCMD_LEN - (szWalk - szCmd));
            fDataPort = TRUE;
            break;

        default:
            // This should have been caught in the proxy
            TBD_ASSERT(FALSE);

        case RIL_CALLTYPE_UNKNOWN:
            hr = E_INVALIDARG;
            goto Error;
    }

    szWalk = strchr(szWalk, '\0');  // NO_TYPO: 27
    TBD_ASSERT(NULL != szWalk);

    szDialStringStart = szWalk;

    // Copy the number in
    szAddrWalk = szAddress;
    while(*szAddrWalk)
    {
        // Only allow characters in the set specified by GSM 07.07 section 6.2
        if (strchr("1234567890*#+ABCD,TP!W@",*szAddrWalk))
        {
            *szWalk++ = *szAddrWalk;
        }
        szAddrWalk++;
    }

    pHandle->GetDevice()->SetLastDialedNumber(szAddress);
    // If the dial string is really long, make a note in case the
    // command fails with a generic error. We can then guess that
    // the real error is that the dial string was too long.
    if ((szWalk - szDialStringStart) > MAX_DIAL_STRING_LENGTH)
    {
        dwDialOpts |= CMDOPT_LONGDIALSTRING;
    }

    // Remember to paste on the terminating '\0'
    *szWalk='\0';

    if (dwOptions & RIL_DIALOPT_CLOSEDGROUP) {
        (void)strncpyz(szWalk, "G", MAX_ATCMD_LEN - (szWalk - szCmd));
        szWalk++;
    }

    if (dwOptions & RIL_DIALOPT_RESTRICTID) {
        (void)strncpyz(szWalk, "I", MAX_ATCMD_LEN - (szWalk - szCmd));
        szWalk++;
    }
    else if (dwOptions & RIL_DIALOPT_PRESENTID) {
        (void)strncpyz(szWalk, "i", MAX_ATCMD_LEN - (szWalk - szCmd));
        szWalk++;
    }

    // If it's a data call, terminate with a ; character
    if (dwType==RIL_CALLTYPE_VOICE)
    {
        (void)strncpyz(szWalk, ";", MAX_ATCMD_LEN - (szWalk - szCmd));
        szWalk++;
    }
    else if (dwType==RIL_CALLTYPE_DATA)
    {
        dwDialOpts |= CMDOPT_DELAYCONNECTRSP;
    }

    (void)strncpyz(szWalk, "\r", MAX_ATCMD_LEN - (szWalk - szCmd));

    g_fCallWaiting = FALSE;

    if (fDataPort == TRUE) {
      if (!QueueCmd(DATA_PORT, pHandle, szCmd, dwDialOpts, APIID_DIAL, NULL, pnd, hr)) {
        hr = E_FAIL;
        goto Error;
      }
    } else {
      ActivateMicrophone(pHandle->GetDevice());
      g_LastATD = GetTickCount();
      if (!QueueCmdWithTimeout(COMMAND_PORT, pHandle, szCmd, NULL, dwDialOpts|CMDOPT_KEEPORDER, APIID_DIAL, NULL, pnd, hr, 0, 0, 0, NULL, NULL, NULL, PreInitDialing)) {
        TestLastActiveCall(pHandle->GetDevice());
        hr = E_FAIL;
        goto Error;
      }
    }

    pnd = NULL;

Error:
    return hr;
}


//
//
//
HRESULT RILDrv_Answer(DWORD dwParam)
{
    TBD_FUNCTION(RILDrv_Answer);
    CNotificationData* pnd = NULL;
    RILCONNECTINFO rci; memset(&rci,0,sizeof(rci)); // zero struct
    HRESULT hr = S_OK;
    BOOL bCallActive = FALSE;
    BOOL bIsCSDCall = FALSE;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    if (g_fEmergencyCall) {
        g_fEmergencyCall = FALSE;
    }

    g_fReleaseHeldCall = FALSE;

    // Set up the voice connect notification. If  incoming call is a data call,
    //    we will get a "CONNECT" instead of "OK", which will prevent this notification
    //    from being sent
    pnd = new CNotificationData;
    if (pnd) {
        rci.cbSize = sizeof(RILCONNECTINFO);
        rci.dwParams = RIL_PARAM_CNI_CALLTYPE;
        rci.dwCallType = RIL_CALLTYPE_VOICE;
        if (!pnd->InitFromRealBlob(RIL_NOTIFY_CONNECT, &rci, sizeof(RILCONNECTINFO))) {
            delete pnd;
            pnd = NULL;
        }
    }

    EnterCriticalSection(&g_CallInfoSection);
    if (g_iNrCallInfo > 0) {
        INT i;

        for (i=0; i<g_iNrCallInfo; i++) {
            if (g_CallInfo[i].dwStatus==RIL_CALLSTAT_ALERTING) {
                if (g_CallInfo[i].dwType == RIL_CALLTYPE_DATA || g_CallInfo[i].dwType == RIL_CALLTYPE_FAX)
                    bIsCSDCall = FALSE;
            }
            if (g_CallInfo[i].dwStatus==RIL_CALLSTAT_ACTIVE || g_CallInfo[i].dwStatus==RIL_CALLSTAT_ONHOLD) {
                bCallActive = TRUE;
            }
        }
    }
    LeaveCriticalSection(&g_CallInfoSection);

    if (bIsCSDCall)
    {
        // If data port is already in use we cannot accept any CSD call.
        if (pHandle->GetDevice()->GetComDevice(DATA_PORT)->FDataMode())
        {
            hr = E_FAIL;
            goto Error;
        }
        if (!QueueCmd(DATA_PORT, pHandle, "ATA\r", CMDOPT_ANSWER, APIID_ANSWER, NULL, pnd, hr)) {
            hr = E_FAIL;
            goto Error;
        }
    }
    else
    {
        ActivateMicrophone(pHandle->GetDevice());
        if (!QueueCmdWithTimeout(COMMAND_PORT, pHandle, bCallActive ? "AT+CHLD=2\r" : "ATA\r", NULL, CMDOPT_ANSWER, APIID_ANSWER, NULL, pnd, hr, 0, 0, 0, NULL, NULL, NULL, PreInitAnswer)) {
        //if (!QueueCmdPriority(pHandle, bCallActive ? "AT+CHLD=2\r" : "ATA\r", CMDOPT_ANSWER, APIID_ANSWER, NULL, pnd, hr, NULL, NULL, NULL, PreInitAnswer)) {
            hr = E_FAIL;
            goto Error;
        }
    }
    pnd = NULL;

Error:
    return hr;
}

//
//
//
void RHADeactivateVoicePath(CRilHandle *pRILHandle)
{
    BOOL bCallActive = FALSE;

    if (g_iNrCallInfo > 0) {
        EnterCriticalSection(&g_CallInfoSection);
        if (g_iNrCallInfo > 0) {
            INT i;

            for (i=0; i<g_iNrCallInfo; i++) {
                if (g_CallInfo[i].dwType != RIL_CALLTYPE_DATA && (g_CallInfo[i].dwStatus==RIL_CALLSTAT_ACTIVE || g_CallInfo[i].dwStatus==RIL_CALLSTAT_ONHOLD)) {
                    bCallActive = TRUE;
                    break;
                }
            }
        }
        LeaveCriticalSection(&g_CallInfoSection);
    }

    if(g_fAudioPathActivated && !bCallActive)
    {
        if (pRILHandle!=NULL && !g_bRadioOff && g_fInitMicroTemp)
        {
            // If we manually control the microphone power, we now have to turn the power off using
            // the AT^SNFM command.
            //QueueInternalCmd(URC_PORT, pRILHandle, "AT^SNFM=,2\r", CMDOPT_NONE, APIID_NONE, GetSNFMTimeout(), 0, 0);
        }
        RHAFunctionList.pfnRHA_DeactivateVoicePath();
        g_fAudioPathActivated = FALSE;
        g_fNoMuteCmdSoFar = TRUE;
    }
}

//
//
//
void TestLastActiveCall(CRilHandle* const pRilDevice)
{
    if (g_iNrCallInfo > 0) {
        RHADeactivateVoicePath(pRilDevice);
    }
    else if (g_iNrCallInfo < 0) {
        QueueInternalCmd(COMMAND_PORT, pRilDevice, "AT+CLCC\r", CMDOPT_NONE, APIID_GETCALLLIST, 0, 0, 0, ParseGetCallList, NULL, pRilDevice);
    }
}

DWORD WINAPI ReCheckCallStatusThreadProc(LPVOID pVoid)
{
    CRilHandle* pHandle = (CRilHandle *)pVoid;

    // Sleep a little bit to allow other notification which may be pending to be sent.
   Sleep(100);

    // Check the current call status and notifiy the MMI if actual call status differs from the chached status.
    QueueInternalCmd(URC_PORT, pHandle, "AT+CLCC\r", CMDOPT_NONE, APIID_GETCALLLIST, 0, 0, 0, ParseGetCallListCheck, NULL, pVoid);

    return 0;
}

//
//
//
static HRESULT ParseATHError(LPCSTR szRsp, LPVOID pParam)
{
    // Due to odd circumstances  it might happen, that we either miss an URC with a call status change or
    // the module didn't send one. In this case we start a thread to re-check the current call status.?
    CreateThread(NULL, 0, ReCheckCallStatusThreadProc, pParam, 0, 0);
    return S_OK;
}

//
//
//
HRESULT RILDrv_Hangup(DWORD dwParam)
{
    TBD_FUNCTION(RILDrv_Hangup);
    CNotificationData* pnd = NULL;
    BOOL fSuccess;
    HRESULT hr = S_OK;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    if (g_fEmergencyCall) {
        g_fEmergencyCall = FALSE;
    }

#ifdef RIL_FAKECSQ
    g_fFakeSignalStrength = FALSE;
#endif

    g_fReleaseHeldCall = FALSE;

    pnd = new CNotificationData;
    if (pnd) {
        if (pHandle->FPreferred()) {
            fSuccess = pnd->InitFromRealBlob(RIL_NOTIFY_EMERGENCYHANGUP, NULL, 0);
        } else {
#ifdef SIEMENS_MAGNETO
            fSuccess = pnd->InitFromDWORDBlob(RIL_NOTIFY_DISCONNECT, RIL_DISCINIT_LOCAL);
#else
            fSuccess = pnd->InitFromRealBlob(RIL_NOTIFY_CALLSTATECHANGED, NULL, 0);
#endif
        }
        if (!fSuccess) {
            delete pnd;
            pnd = NULL;
        }
    }

    RHADeactivateVoicePath(pHandle->GetDevice());

    g_fCallWaiting = FALSE;

    if (!QueueCmdPriority(pHandle, "ATH\r", CMDOPT_HANGUP, APIID_HANGUP, NULL, pnd, hr, ParseATHError, (LPVOID)(pHandle->GetDevice()))) {
        hr = E_FAIL;
        goto Error;
    }
    pnd = NULL;

Error:
    return hr;
}

static HRESULT CheckForModuleReadyForVTS(CRilHandle *pHandle, COM_PORT_TYPE iPort)
{
    CCommand *pCmd;
    HRESULT hr = S_OK;
    BOOL bFound;

    // Special problem with MC75 V2.001: if AT+VTS is sent shortly after an ATD during multiparty call,
    // the AT command interface may becamo blocked, so we have to avoid it.
    if (g_dwModemType == MODEMTYPE_SIEMENS_MC75 && g_dwModemVersion == 2001)
    {

        // Do not send AT+CTV id ATD is ongoing.
        pCmd = pHandle->GetCurrentCommand(iPort);
        if (pCmd != NULL)
        {
            if (pCmd->FDial())
                hr = RIL_E_NETWKTEMPFAILURE;
        }

        // Also do not send AT+VTS if ATD is in the queue, since this would cancel the tone anyway
        if (hr == S_OK)
        {
            bFound = FALSE;
            g_pCmdQ[COMMAND_PORT]->Enum(IsDialCommandInQueue, (DWORD)&bFound, FALSE);
            if (bFound)
                hr = RIL_E_NETWKTEMPFAILURE;
        }

        // If ATD has just been issued, but the ^SLCC hasn't shown up yet we shouldn't send AT+VTS
        // since this would anyhow be send to the wrong caller.
        if (g_LastATD != 0) {
            hr = RIL_E_NETWKTEMPFAILURE;
        }

    }

    return hr;
}

//
//
//
HRESULT RILDrv_SendDTMF(DWORD dwParam, LPCSTR lpszChars, DWORD dwDuration)
{
    TBD_FUNCTION(RILDrv_SendDTMF);

    TBD_ASSERT(NULL != lpszChars);
    TBD_ASSERT(0 < strlen(lpszChars));

    UINT NumVals;
    UINT nValue;
    DWORD dwTimeout;
    char szCmd[MAX_ATCMD_LEN];
    LPSTR szWalk = szCmd;
    LPCSTR pchChars;
    HRESULT hr = S_OK;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);

    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    if ((dwDuration < 300) || (dwDuration == RIL_DTMFDURATION_DEFAULT))
    {
        dwDuration = 300;
    }

    nValue = (dwDuration + 50 ) / 100;

    pchChars = lpszChars;
    while (*pchChars) {
        hr = S_OK;


        (void)_snprintfz(szWalk, MAX_ATCMD_LEN - (szWalk - szCmd), "AT+VTD=%u;", nValue);

        szWalk = strchr(szWalk, '\0');  // NO_TYPO: 27
        TBD_ASSERT(NULL != szWalk);

        NumVals=0;
        for (; *pchChars && NumVals < MAX_DTMF_TONES_PER_LINE; pchChars++) {
            NumVals++;
            (void)_snprintfz(szWalk, MAX_ATCMD_LEN - (szWalk - szCmd), "+VTS=%c;", *pchChars);
            szWalk = strchr(szWalk, '\0');  // NO_TYPO: 27
            TBD_ASSERT(NULL != szWalk);
        }
        szWalk--;  // delete the unnecessary ;
        (void)strncpyz(szWalk, "\r", MAX_ATCMD_LEN - (szWalk - szCmd));  // NO_TYPO: 30

        // Pick a timeout value to use
        dwTimeout = (2 * NumVals * dwDuration) + g_TimeoutAPIDefault;
        TBD_OUTPUT(TBDCT_INFO, TBDOL_INFORMATION, TEXT("RILDrv_SendDTMF : Sending DTMF, NumVals=%d, dwDuration=%d, Timeout = %d ms"), NumVals, dwDuration, dwTimeout);

        // Problem with ADI modules: AT+VTS and call control commands (AT+CHLD, ATA, ATH) cannot be mixed across the
        // multiplexer channels, so we have to send this command on the priority port although this may block the receiving
        // of SMS during long DTMF tone sequences. Fortunately the Microsoft MMI is not able to generate DTMF sequences
        // (it generates only single tones), so this is not a real problem.
        if (IS_ADI)
        {
            if (!QueueCmdPriority(pHandle, szCmd, CMDOPT_SUPPRESSLOGGING, APIID_SENDDTMF, NULL, NULL, hr, NULL, NULL, NULL, CheckForModuleReadyForVTS, dwTimeout)) {
                hr = E_FAIL;
                goto Error;
            }
        }
        else
        {
            if (!QueueCmdWithTimeout(COMMAND_PORT, pHandle, szCmd, NULL, CMDOPT_SUPPRESSLOGGING, APIID_SENDDTMF, NULL, NULL, hr, dwTimeout, 0, 0)) {
                hr = E_FAIL;
                goto Error;
            }
        }
        szWalk = szCmd;
    }

Error:
    return hr;
}

static BOOL IsDialingInAction(CRilHandle *pRilHandle)
{
    CCommand *pCmd;
    BOOL bFound;

    if (pRilHandle == NULL)
        return FALSE;
    
    // First check, if there are any 
    bFound = FALSE;
    pCmd = pRilHandle->GetCurrentCommand(COMMAND_PORT);
    if (pCmd != NULL) {
        if (pCmd->FDial()) {
            return TRUE;
        }
    }
    g_pCmdQ[COMMAND_PORT]->Enum(IsDialCommandInQueue, (DWORD)&bFound, FALSE);
    return bFound;
}

//
//
//
static HRESULT ParseGetCallListInternal(BOOL bDoCheck, LPCSTR szRsp, void*& pBlob, UINT& cbBlob, LPVOID pParam)
{
    TBD_FUNCTION(ParseGetCallListInternal);
    UINT nNoCalls = 0;
    UINT nValue;
    UINT nUsed = 0;
    UINT nAllocated = 0;
    char szAddress[MAXLENGTH_ADDRESS];
    RILCALLINFO* rgrci = NULL;
    HRESULT hr = S_OK;
    BOOL bSuccess;
    BOOL bIncomingCall = FALSE;
    BOOL rgfNewCallsInProgress[MAX_TRACKED_CALL_ID] = { 0 };  // Initialize to FALSE
    BOOL bContinue = FALSE;
    CRilHandle *pDevice;

    pDevice = (CRilHandle *)pParam;

    pBlob = NULL;
    cbBlob = 0;

    // Parse "<prefix>"
    if (!ParseRspPrefix(szRsp, szRsp)) {
        hr = E_FAIL;
        goto Error;
    }

//  <cr><lf>+CLCC: 1,0,0,0,0,"01215016024",129,"W,M"<cr><lf>+CLCC: 2,1,5,0,0,"07976723389",129<cr><lf>0<cr>

    // Parse "+CLCC: "
    while (MatchStringBeginning(szRsp, "+CLCC: ", szRsp)) {
        if (nUsed == nAllocated) {
            if (!AllocateOrReallocateStorage((BYTE**)&rgrci, sizeof(RILCALLINFO), nUsed, &nAllocated, CALLCTRL_ALLOC_NUMBER)) {
                hr = E_OUTOFMEMORY;
                goto Error;
            }
        }

        nNoCalls ++;

        memset(&rgrci[nUsed], 0x00, sizeof(RILCALLINFO));
        rgrci[nUsed].cbSize = sizeof(RILCALLINFO);
        rgrci[nUsed].raAddress.cbSize = sizeof(RILADDRESS);

        // Parse "<id>"
        if (!ParseUInt(szRsp, TRUE, nValue, szRsp)) {
            goto Continue;
        }
        rgrci[nUsed].dwID = nValue;
        rgrci[nUsed].dwParams |= RIL_PARAM_CI_ID;

        // Track the call
        TBD_ASSERT((0 <= nValue) && (nValue < ARRAY_LENGTH(rgfNewCallsInProgress))); // Otherwise, it may be necessary to increase MAX_TRACKED_CALL_ID
        if ((0 <= nValue) && (nValue < ARRAY_LENGTH(rgfNewCallsInProgress))) {
            rgfNewCallsInProgress[nValue] = TRUE;
        }

        // Parse ",<direction>"
        if (!MatchStringBeginning(szRsp, ",", szRsp) ||
            !ParseUIntAndVerifyAbove(szRsp, TRUE, 2, nValue, szRsp)) {
            goto Continue;
        }
        rgrci[nUsed].dwDirection = (nValue ? RIL_CALLDIR_INCOMING : RIL_CALLDIR_OUTGOING);
        rgrci[nUsed].dwParams |= RIL_PARAM_CI_DIRECTION;

        // Parse ",<status>"
        if (!MatchStringBeginning(szRsp, ",", szRsp) ||
            !ParseUIntAndVerifyAbove(szRsp, TRUE, NUM_CALLSTATS, nValue, szRsp)) {
            goto Continue;
        }
        rgrci[nUsed].dwStatus = g_rgdwCallStats[nValue];
        rgrci[nUsed].dwParams |= RIL_PARAM_CI_STATUS;

        switch (g_rgdwCallStats[nValue])
        {
        case RIL_CALLSTAT_INCOMING:
        case RIL_CALLSTAT_WAITING:
            bIncomingCall = TRUE;
        }

        // Parse ",<type>"
        if (!MatchStringBeginning(szRsp, ",", szRsp) ||
            !ParseUIntAndVerifyAbove(szRsp, TRUE, NUM_CALLTYPES, nValue, szRsp)) {
            goto Continue;
        }
        rgrci[nUsed].dwType = g_rgdwCallTypes[nValue];
        rgrci[nUsed].dwParams |= RIL_PARAM_CI_TYPE;

        // Parse ",<multiparty>"
        if (!MatchStringBeginning(szRsp, ",", szRsp) ||
            !ParseUIntAndVerifyAbove(szRsp, TRUE, 2, nValue, szRsp)) {
            goto Continue;
        }
        rgrci[nUsed].dwMultiparty = (nValue ? RIL_CALL_MULTIPARTY : RIL_CALL_SINGLEPARTY);
        rgrci[nUsed].dwParams |= RIL_PARAM_CI_TYPE;

        // Parse ","
        if (MatchStringBeginning(szRsp, ",", szRsp)) {
            // Parse "<address>,<type>"
            if (ParseString(szRsp, szAddress, MAXLENGTH_ADDRESS, szRsp)) {
                if(!MatchStringBeginning(szRsp, ",", szRsp))
                    bContinue = TRUE;

                if(!ParseUIntAndVerifyAbove(szRsp, FALSE, 0x1000, nValue, szRsp))
                    bContinue = TRUE;

                nValue = (nValue > 0x100) ? nValue/10 : nValue;

                if(!StringToRILAddress(szAddress, (BYTE)nValue, rgrci[nUsed].raAddress))
                    bContinue = TRUE;

                if(bContinue)
                    goto Continue;

                rgrci[nUsed].dwParams |= RIL_PARAM_CI_ADDRESS;
            }
            else {
                // If we couldn't parse an address, then it might be empty,
                // meaning the ID is blocked. Since the address parameter
                // is present, make sure the type also exists before continuing.
                if (!MatchStringBeginning(szRsp, ",", szRsp)                 ||
                    !ParseUIntAndVerifyAbove(szRsp, FALSE, 0x100, nValue, szRsp)) {
                    goto Continue;
                }
            }

            // Parse ","
            if (MatchStringBeginning(szRsp, ",", szRsp)) {
                // Parse "<description>"
                if (!ParseQuotedEncodedString(ENCODING_UCS2, szRsp, rgrci[nUsed].wszDescription, rgrci[nUsed].wszDescription + MAXLENGTH_DESCRIPTION)) {
                    goto Continue;
                }
                rgrci[nUsed].dwParams |= RIL_PARAM_CI_DESCRIPTION;
            }
        }

        // Increment the array index
        nUsed++;

Continue:
        // Find "<postfix>"
        bSuccess = ParseRspPostfix(szRsp, szRsp);

        if (!bSuccess)
        {
            hr = E_FAIL;
            goto Error;
        }
    }


    if (bDoCheck)
    {
        BOOL bDoBroadcast = FALSE;

        EnterCriticalSection(&g_CallInfoSection);

        if ((UINT)g_iNrCallInfo != nUsed) {
            g_iNrCallInfo = nUsed;
            if (g_iNrCallInfo > 0) {
                bDoBroadcast = TRUE;

                if (AllocateOrReallocateStorage((BYTE**)&rgrci, sizeof(RILCALLINFO), 0, &nAllocated, max(CALLCTRL_ALLOC_NUMBER,g_iNrCallInfo))) {
                    if (g_iNrCallInfo > (INT)nAllocated) {
                        hr = E_OUTOFMEMORY;
                        LeaveCriticalSection(&g_CallInfoSection);
                        goto Error;
                    }
                    memcpy(g_CallInfo, rgrci, g_iNrCallInfo * sizeof(RILCALLINFO));
                }
            }
        }
        LeaveCriticalSection(&g_CallInfoSection);

        if (bDoBroadcast)
            pDevice->BroadcastRealBlobNotification(RIL_NOTIFY_CALLSTATECHANGED, NULL, 0);
    }

    // Now that we've succeeded parsing the notification, record the new call states
    C_ASSERT(sizeof(g_rgfCallsInProgress) == sizeof(rgfNewCallsInProgress));
    CopyMemory(g_rgfCallsInProgress, rgfNewCallsInProgress, sizeof(g_rgfCallsInProgress));

    if (!bDoCheck)
    {
        SetBacklightIncomingCall(bIncomingCall);
    }

    // If there are no calls left, we should check, that the voice path is really deactivated.
    if (nNoCalls == 0)
    {
        RHADeactivateVoicePath(pDevice);
    }

    pBlob = (void*)rgrci;
    cbBlob = nUsed * sizeof(RILCALLINFO);

Error:
    if (FAILED(hr) && (rgrci != NULL)) {
        FreeBlob(rgrci);
    }
    return hr;
}

static HRESULT ParseGetCallList(LPCSTR szRsp, void*& pBlob, UINT& cbBlob, LPVOID pParam)
{
    return ParseGetCallListInternal(FALSE, szRsp, pBlob, cbBlob, pParam);
}

static HRESULT ParseGetCallListCheck(LPCSTR szRsp, void*& pBlob, UINT& cbBlob, LPVOID pParam)
{
    return ParseGetCallListInternal(TRUE, szRsp, pBlob, cbBlob, pParam);
}

//
//
//
static HRESULT ParseGetFakeCallList(LPCSTR szRsp, void*& pBlob, UINT& cbBlob, LPVOID pParam)
{
    TBD_FUNCTION(ParseGetFakeCallList);
    UINT nUsed = 0;
    UINT nAllocated = 0;
    RILCALLINFO* rgrci = NULL;
    HRESULT hr = S_OK;
    BOOL bIncomingCall = FALSE;
    BOOL rgfNewCallsInProgress[MAX_TRACKED_CALL_ID] = { 0 };  // Initialize to FALSE

    pBlob = NULL;
    cbBlob = 0;

    if (!AllocateOrReallocateStorage((BYTE**)&rgrci, sizeof(RILCALLINFO), nUsed, &nAllocated, CALLCTRL_ALLOC_NUMBER)) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    memset(&rgrci[nUsed], 0x00, sizeof(RILCALLINFO));
    rgrci[nUsed].cbSize = sizeof(RILCALLINFO);
    StringToRILAddress(g_szEmergencyNo, (BYTE)129, rgrci[nUsed].raAddress);
    rgrci[nUsed].dwParams |= RIL_PARAM_CI_ADDRESS;
    rgrci[nUsed].raAddress.cbSize = sizeof(RILADDRESS);
    rgrci[nUsed].dwID = 1;
    rgrci[nUsed].dwParams |= RIL_PARAM_CI_ID;
    // Track the call
    rgfNewCallsInProgress[1] = TRUE;
    rgrci[nUsed].dwDirection = RIL_CALLDIR_OUTGOING;
    rgrci[nUsed].dwParams |= RIL_PARAM_CI_DIRECTION;
    rgrci[nUsed].dwStatus = RIL_CALLSTAT_ACTIVE;
    rgrci[nUsed].dwParams |= RIL_PARAM_CI_STATUS;
    rgrci[nUsed].dwType = RIL_CALLTYPE_VOICE;
    rgrci[nUsed].dwParams |= RIL_PARAM_CI_TYPE;
    rgrci[nUsed].dwMultiparty = RIL_CALL_SINGLEPARTY;
    rgrci[nUsed].dwParams |= RIL_PARAM_CI_TYPE;
    nUsed++;

    // Now that we've succeeded parsing the notification, record the new call states
    C_ASSERT(sizeof(g_rgfCallsInProgress) == sizeof(rgfNewCallsInProgress));
    CopyMemory(g_rgfCallsInProgress, rgfNewCallsInProgress, sizeof(g_rgfCallsInProgress));

    SetBacklightIncomingCall(bIncomingCall);

    pBlob = (void*)rgrci;
    cbBlob = nUsed * sizeof(RILCALLINFO);

Error:
    if (FAILED(hr) && (rgrci != NULL)) {
        FreeBlob(rgrci);
    }
    return hr;
}

volatile BOOL g_fFakeEndCLCC = FALSE;

static HRESULT ParseGetCallListSLCC(LPCSTR szRsp, void*& pBlob, UINT& cbBlob, LPVOID pParam)
{
    TBD_FUNCTION(ParseGetCallListSLCC);
    INT nNoCalls, i;
    UINT nAllocated = 0;
    RILCALLINFO* rgrci = NULL;
    HRESULT hr = S_OK;
    BOOL bIncomingCall = FALSE;
    BOOL rgfNewCallsInProgress[MAX_TRACKED_CALL_ID] = { 0 };  // Initialize to FALSE
    BOOL bContinue = FALSE;

    pBlob = NULL;
    cbBlob = 0;
    Sleep(200);         // For Radiotest program only: additional waiting period to simulate execution time of AT+CLCC command.
    EnterCriticalSection(&g_CallInfoSection);
    if (g_iNrCallInfo > 0) {
        if (!AllocateOrReallocateStorage((BYTE**)&rgrci, sizeof(RILCALLINFO), 0, &nAllocated, max(CALLCTRL_ALLOC_NUMBER,g_iNrCallInfo))) {
            hr = E_OUTOFMEMORY;
            LeaveCriticalSection(&g_CallInfoSection);
            goto Error;
        }
        if (g_iNrCallInfo > (INT)nAllocated) {
            hr = E_OUTOFMEMORY;
            LeaveCriticalSection(&g_CallInfoSection);
            goto Error;
        }
        memcpy(rgrci, g_CallInfo, g_iNrCallInfo * sizeof(RILCALLINFO));
    }

    nNoCalls = g_iNrCallInfo;
    LeaveCriticalSection(&g_CallInfoSection);

    for (i=0; i < nNoCalls; i++) {
        rgfNewCallsInProgress[i] = TRUE;
        switch (rgrci[i].dwStatus)
        {
        case RIL_CALLSTAT_INCOMING:
        case RIL_CALLSTAT_WAITING:
            bIncomingCall = TRUE;
        }
    }

    // Now that we've succeeded parsing the notification, record the new call states
    C_ASSERT(sizeof(g_rgfCallsInProgress) == sizeof(rgfNewCallsInProgress));
    CopyMemory(g_rgfCallsInProgress, rgfNewCallsInProgress, sizeof(g_rgfCallsInProgress));

    SetBacklightIncomingCall(bIncomingCall);

    // If there are no calls left, we should check, that the voice path is really deactivated.
    if (nNoCalls == 0)
    {
        RHADeactivateVoicePath((CRilHandle *)pParam);
    }

    pBlob = (void*)rgrci;
    cbBlob = nNoCalls * sizeof(RILCALLINFO);

Error:
    if (FAILED(hr) && (rgrci != NULL)) {
        FreeBlob(rgrci);
    }
    return hr;
}

//
//
//
HRESULT RILDrv_GetCallList(DWORD dwParam)
{
    TBD_FUNCTION(RILDrv_GetCallList);
    HRESULT hr = S_OK;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);

    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }
    Sleep(50);        // Give the response thread time to parse all URCs

    if (g_fEmergencyCall) {
        if (!QueueCmd(URC_PORT, pHandle, NULL, CMDOPT_NOOP, APIID_GETCALLLIST, ParseGetFakeCallList, NULL, hr)) {
            hr = E_FAIL;
            goto Error;
        }
        g_fFakeEndCLCC = TRUE;
    } else {
        if (g_fFakeEndCLCC) {
            if (!QueueCmd(URC_PORT, pHandle, "AT\r", CMDOPT_NONE, APIID_GETCALLLIST, ParseGetCallList, NULL, hr, NULL, pHandle->GetDevice())) {
                hr = E_FAIL;
                goto Error;
            }
            g_fFakeEndCLCC = FALSE;
        } else {

            if (g_iNrCallInfo > -1) {
                INT i;

                // There is no use to query the call state while the ATD is still in queue, so we will block this call
                // until the command is sent.
                while (!g_bRadioOff) {
                    if (!IsDialingInAction(pHandle->GetDevice()))
                        break;
                    Sleep(50);
                }
                
                // We have to prevent, that RILDrv_GetCallList() is executed while a dial command is ongoing
                // or still in the queue. Otherwise it might happen, that we report to the MMI, that there is no
                // dialing in progress and the MMI would assume, the dialing command has been aborted,
                // while actually it hadn't started yet. This would lead to the situation, that the module establishes
                // the call, while the MMI shows "call ended".
                i = 0;
                while (g_LastATD != 0) {
                    Sleep(50);
                    if (i++ > 50)   // Typically this will take 100ms, 2500ms should be the worst case.
                        break;
                }
                g_LastATD = 0;

                if (!QueueCmd(URC_PORT, pHandle, NULL, CMDOPT_NOOP, APIID_GETCALLLIST, ParseGetCallListSLCC, NULL, hr, NULL, (LPVOID)pHandle->GetDevice())) {
                    hr = E_FAIL;
                    goto Error;
                }
            } else {
                if (!QueueCmd(ORDER_PRIORITY_PORT, pHandle, "AT+CLCC\r", CMDOPT_KEEPORDER, APIID_GETCALLLIST, ParseGetCallList, NULL, hr, NULL, pHandle->GetDevice())) {
                    hr = E_FAIL;
                    goto Error;
                }
            }
        }
    }

Error:
    return hr;
}


//
//
//
HRESULT RILDrv_ManageCalls(DWORD dwParam, DWORD dwCommand, DWORD dwID)
{
    TBD_FUNCTION(RILDrv_ManageCalls);
    CNotificationData* pnd = NULL;
    BOOL fNeedNotification = TRUE;
    char szCmd[MAX_ATCMD_LEN];
    DWORD dwOpt = CMDOPT_NONE;
    HRESULT hr = S_OK;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    switch (dwCommand)
    {
        case RIL_CALLCMD_RELEASEHELD:
            (void)strncpyz(szCmd, "AT+CHLD=0\r", MAX_ATCMD_LEN);
            dwOpt |= CMDOPT_HANGUP;  // Necessary when attempting to end a single outgoing call
            g_fReleaseHeldCall = TRUE;
            break;

        case RIL_CALLCMD_RELEASEACTIVE_ACCEPTHELD:
            (void)strncpyz(szCmd, "AT+CHLD=1\r", MAX_ATCMD_LEN);
            break;

        case RIL_CALLCMD_RELEASECALL:
            if (dwID==0)
            {
                // Id of 0 is a magic value which says to release a call being dialed while
                // another call is on hold.
                // This activates code in HangupThreadProc to force it to send AT\r to the modem
                // to try to abort a call being dialed.
                // We don't need to send a command to the modem because
                // HangupThreadProc's AT\r will cancel the dial.
                (void)strncpyz(szCmd, "AT\r", MAX_ATCMD_LEN);
                dwOpt = CMDOPT_HANGUP | CMDOPT_NOOP;
            }
            else if (9 == dwID)
            {
                // This is a special-case to enable us to pass FTA case GSM 51.010 31.4.4.2 which requires
                // a way to end all calls in one operation (i.e., "19<SEND>")
                (void)strncpyz(szCmd, "ATH\r", MAX_ATCMD_LEN);
            }
            else if (8 == dwID)
            {
                // This is a special-case to enable us to pass FTA case GSM 51.010 31.4.4.1.2.4 which requires
                // a way to end a held conference call in one operation (i.e., "18<SEND>")
                (void)strncpyz(szCmd, "AT+CHLD=11;+CHLD=12\r", MAX_ATCMD_LEN);
            }
            else
            {
                TBD_ASSERT((0 <= dwID) && (dwID < ARRAY_LENGTH(g_rgfCallsInProgress)));  // Otherwise, if may be necessary to increase MAX_TRACKED_CALLS
                (void)_snprintfz(szCmd, MAX_ATCMD_LEN, "AT+CHLD=1%u\r", dwID);
            }
            break;

        case RIL_CALLCMD_HOLDACTIVE_ACCEPTHELD:
            (void)strncpyz(szCmd, "AT+CHLD=2\r", MAX_ATCMD_LEN);
            break;

        case RIL_CALLCMD_HOLDALLBUTONE:
            TBD_ASSERT((0 <= dwID) && (dwID < ARRAY_LENGTH(g_rgfCallsInProgress)));  // Otherwise, it may be necessary to increase MAX_TRACKED_CALLS
            (void)_snprintfz(szCmd, MAX_ATCMD_LEN, "AT+CHLD=2%u\r", dwID);
            fNeedNotification = FALSE;
            break;

        case RIL_CALLCMD_ADDHELDTOCONF:
            (void)strncpyz(szCmd, "AT+CHLD=3\r", MAX_ATCMD_LEN);
            fNeedNotification = FALSE;
            break;

        case RIL_CALLCMD_ADDHELDTOCONF_DISCONNECT:
            (void)strncpyz(szCmd, "ATD4;\r", MAX_ATCMD_LEN);
            fNeedNotification = FALSE;
            break;

        case RIL_CALLCMD_INVOKECCBS:
            (void)strncpyz(szCmd, "ATD5;\r", MAX_ATCMD_LEN);
            fNeedNotification = FALSE;
            break;

        default:
            // The proxy code should never let us get here
            TBD_ASSERT(FALSE);
    }

    if (fNeedNotification) {
        pnd = new CNotificationData;
        if (pnd && !pnd->InitFromRealBlob(RIL_NOTIFY_CALLSTATECHANGED, NULL, 0)) {
            delete pnd;
            pnd = NULL;
        }
    }

    g_fCallWaiting = FALSE;

    if (hr == S_OK) {
        if (!QueueCmdPriority(pHandle, szCmd, dwOpt, APIID_MANAGECALLS, NULL, pnd, hr)) {
            hr = E_FAIL;
            goto Error;
        }
    }
    pnd = NULL;

Error:
    return hr;
}


//
//
//
HRESULT RILDrv_TransferCall(DWORD dwParam, const RILADDRESS* lpAddress, const RILSUBADDRESS* lpSubAddress)
{
    TBD_FUNCTION(RILDrv_TransferCall);
    BYTE bTypeOfAddress;
    char szAddress[MAXLENGTH_ADDRESS];
    char szCmd[MAX_ATCMD_LEN];
    HRESULT hr = S_OK;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle || !lpAddress) {
        hr = E_FAIL;
        goto Error;
    }

    // The MC75 doesn't have the feature explicit call transfer.
    if (IS_ADI)
        return E_NOTIMPL;

    // Determine address and type-of-address byte
    hr = RILAddressToString(*lpAddress, szAddress, MAXLENGTH_ADDRESS, bTypeOfAddress);
    if (FAILED(hr)) {
        goto Error;
    }
    // Siemens specific: the Siemens modules don't offer the AT+CTFR command, but they can
    // perform a call deflection with the ATD4*...; command.
    (void)_snprintfz(szCmd, MAX_ATCMD_LEN, "ATD4*%s;\r", szAddress);

    if (!QueueCmd(COMMAND_PORT, pHandle, szCmd, CMDOPT_NONE, APIID_TRANSFERCALL, NULL, NULL, hr)) {
        hr = E_FAIL;
        goto Error;
    }

Error:
    return hr;
}


//
//
//
static HRESULT ParseGetLineStatusRsp(LPCSTR szRsp, void*& pBlob, UINT& cbBlob, LPVOID pParam)
{
    TBD_FUNCTION(ParseGetLineStatusRsp);
    UINT nValue;
    DWORD* pdwLineStatus = NULL;
    HRESULT hr = S_OK;

    pBlob = NULL;
    cbBlob = 0;

    pdwLineStatus = (DWORD*)AllocBlob(sizeof(DWORD));
    if (!pdwLineStatus) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    memset(pdwLineStatus, 0x00, sizeof(DWORD));

    // Parse "<prefix>+CPAS: <line_status><postfix>"
    if (!ParseRspPrefix(szRsp, szRsp)                                  ||
        !MatchStringBeginning(szRsp, "+CPAS: ", szRsp)                 ||
        !ParseUIntAndVerifyAbove(szRsp, TRUE, NUM_LINESTATS, nValue, szRsp) ||
        !ParseRspPostfix(szRsp, szRsp)) {
        hr = E_FAIL;
        goto Error;
    }
    *pdwLineStatus = g_rgdwLineStats[nValue];

    if(g_fCallWaiting && *pdwLineStatus == RIL_LINESTAT_RINGING)*pdwLineStatus = RIL_LINESTAT_CALLINPROGRESS;

    SetBacklightIncomingCall(RIL_LINESTAT_RINGING == g_rgdwLineStats[nValue]);

    pBlob = (void*)pdwLineStatus;
    cbBlob = sizeof(DWORD);

Error:
    if (FAILED(hr)) {
        FreeBlob(pdwLineStatus);
    }
    return hr;
}

static HRESULT ParseGetLineStatusRspSLCC(LPCSTR szRsp, void*& pBlob, UINT& cbBlob, LPVOID pParam)
{
    TBD_FUNCTION(ParseGetLineStatusRspSLCC);
    INT i;
    DWORD* pdwLineStatus = NULL;
    HRESULT hr = S_OK;

    pBlob = NULL;
    cbBlob = 0;

    pdwLineStatus = (DWORD*)AllocBlob(sizeof(DWORD));
    if (!pdwLineStatus) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    memset(pdwLineStatus, 0x00, sizeof(DWORD));

    EnterCriticalSection(&g_CallInfoSection);
    *pdwLineStatus = RIL_LINESTAT_READY;
    for (i=0; i < g_iNrCallInfo; i++) {
        if (g_CallInfo[i].dwStatus == RIL_CALLSTAT_ACTIVE || g_CallInfo[i].dwStatus == RIL_CALLSTAT_ONHOLD)
            *pdwLineStatus = RIL_LINESTAT_CALLINPROGRESS;
        else if (g_CallInfo[i].dwStatus == RIL_CALLSTAT_INCOMING || g_CallInfo[i].dwStatus == RIL_CALLSTAT_WAITING) {
            *pdwLineStatus = RIL_LINESTAT_RINGING;
            break;
        }
    }
    LeaveCriticalSection(&g_CallInfoSection);
    SetBacklightIncomingCall(RIL_LINESTAT_RINGING == *pdwLineStatus);

    if(g_fCallWaiting && (*pdwLineStatus == RIL_LINESTAT_RINGING)) {
        *pdwLineStatus = RIL_LINESTAT_CALLINPROGRESS;
    }

    pBlob = (void*)pdwLineStatus;
    cbBlob = sizeof(DWORD);

Error:
    if (FAILED(hr)) {
        FreeBlob(pdwLineStatus);
    }
    return hr;
}

//
//
//
HRESULT RILDrv_GetLineStatus(DWORD dwParam)
{
    TBD_FUNCTION(RILDrv_GetLineStatus);
    HRESULT hr = S_OK;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    if (g_iNrCallInfo > -1) {
        INT i;

        // There is no use to query the line state while the ATD is still in queue, so we will block this call
        // until the command is sent.
        while (!g_bRadioOff) {
            if (!IsDialingInAction(pHandle->GetDevice()))
                break;
            Sleep(50);
        }
        
        // We have to prevent, that RILDrv_GetLineStatus() is executed while a dial command is ongoing
        // or still in the queue. Otherwise it might happen, that we report to the MMI, that there is no
        // dialing in progress and the MMI would assume, the dialing command has been aborted,
        // while actually it hadn't started yet. This would lead to the situation, that the module establishes
        // the call, while the MMI shows "call ended".
        // We also have to prevent this function to be executed in case a +CRING URC has shown up, but
        // the corresponding ^SLCC URC hasn't arrived yet. We use g_LastATD for this purpose, too.
        i = 0;
        while (g_LastATD != 0) {
            Sleep(50);
            if (i++ > 50)   // Typically this will take 100ms, 2500ms should be the worst case.
                break;
        }
        g_LastATD = 0;
        
        if (!QueueCmd(URC_PORT, pHandle, NULL, CMDOPT_NOOP, APIID_GETLINESTATUS, ParseGetLineStatusRspSLCC, NULL, hr)) {
            hr = E_FAIL;
            goto Error;
        }
    }
    else {
        if (!QueueCmd(PRIORITY_PORT, pHandle, "AT+CPAS\r", CMDOPT_NONE, APIID_GETLINESTATUS, ParseGetLineStatusRsp, NULL, hr)) {
            hr = E_FAIL;
            goto Error;
        }
    }

Error:
    return hr;
}

//
//
//
HRESULT RILDrv_SetCurrentAddressId(DWORD dwParam, DWORD dwAddressId)
{
    TBD_FUNCTION(RILDrv_SetCurrentAddressId);
#ifdef RIL_ENABLE_ALS
    HRESULT hr = S_OK;
    char szCmd[MAX_ATCMD_LEN];
    CNotificationData* pnd = NULL;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    if (!pHandle->GetDevice()->IsSIMReady()) {
        hr = RIL_E_SIMNOTINSERTED;
        goto Error;
    }
    if (g_dwSIMLockedState != RIL_LOCKEDSTATE_READY && g_dwSIMLockedState != RIL_LOCKEDSTATE_UNKNOWN) {
        hr = RIL_E_SIMPINREQUIRED;
        goto Error;
    }

    pnd = new CNotificationData;
    if (pnd && !pnd->InitFromDWORDBlob(RIL_NOTIFY_CURRENTLINECHANGED, dwAddressId)) {
        delete pnd;
        pnd = NULL;
    }

#ifdef AUXCOM_PORT
    (void)_snprintfz(szCmd, MAX_ATCMD_LEN, "AT^SALS=0,%u\r", dwAddressId+1);
#else
    (void)_snprintfz(szCmd, MAX_ATCMD_LEN, "AT^SALS=1,%u\r", dwAddressId+1);
#endif
    if (!QueueCmd(COMMAND_PORT, pHandle, szCmd, CMDOPT_NONE, APIID_SETCURRENTADDRESSID, NULL, pnd, hr)) {
        hr = E_FAIL;
        goto Error;
    }

Error:
#else
    HRESULT hr = E_NOTIMPL;
#endif
    return hr;
}

//
//
//
#ifdef RIL_ENABLE_ALS
static HRESULT ParseGetCurrentAddressId(LPCSTR szRsp, void*& pBlob, UINT& cbBlob, LPVOID pParam)
{
    TBD_FUNCTION(ParseGetCurrentAddressId);
    UINT nValue;
    UINT nDummy;
    DWORD* pdwAddressId = NULL;
    HRESULT hr = S_OK;

    pBlob = NULL;
    cbBlob = 0;

    pdwAddressId = (DWORD*)AllocBlob(sizeof(DWORD));
    if (!pdwAddressId) {
        hr = E_OUTOFMEMORY;
        goto Error;
    }
    *pdwAddressId = 0;

    // Parse "<prefix>^SALS: <URC>,<addressID><postfix>"
    if (!ParseRspPrefix(szRsp, szRsp)                   ||
        !MatchStringBeginning(szRsp, "^SALS: ", szRsp)  ||
        !ParseUInt(szRsp, TRUE, nDummy, szRsp)          ||
        !MatchStringBeginning(szRsp, ",", szRsp)        ||
        !ParseUInt(szRsp, TRUE, nValue, szRsp)          ||
        !ParseRspPostfix(szRsp, szRsp)) {
        hr = E_FAIL;
        goto Error;
    }
    *pdwAddressId = nValue-1;

    pBlob = (void*)pdwAddressId;
    cbBlob = sizeof(DWORD);

Error:
    if (FAILED(hr)) {
        FreeBlob(pdwAddressId);
    }
    return hr;
}
#endif

//
//
//
HRESULT RILDrv_GetCurrentAddressId(DWORD dwParam)
{
    TBD_FUNCTION(RILDrv_GetCurrentAddressId);
#ifdef RIL_ENABLE_ALS
    HRESULT hr = S_OK;
    CRilInstanceHandle* pHandle = ExtractHandle(dwParam);
    if (!pHandle) {
        hr = E_FAIL;
        goto Error;
    }

    if (!QueueCmd(COMMAND_PORT, pHandle, "AT^SALS?\r", CMDOPT_NONE, APIID_GETCURRENTADDRESSID, ParseGetCurrentAddressId, NULL, hr)) {
        hr = E_FAIL;
        goto Error;
    }

Error:
#else
    HRESULT hr = E_NOTIMPL;
#endif
    return hr;
}

